package io.lb.deng.webflux.web.security;

import com.alibaba.fastjson.JSON;

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

import io.lb.deng.webflux.web.component.JwtTokenProvider;
import io.lb.deng.webflux.web.model.ResponseData;
import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

/**
 * SpringSecurity配置
 * @author DengLibin
 * @date ###### Sat Oct 2 11:46:22 CST 2021
 */
@SpringBootConfiguration
@EnableWebFluxSecurity  //开启WebSecurity支持
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled=true) // 启用权限注解
@Slf4j
public class SecurityConfig {
    
    /**
     * 密码编码器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public ReactiveUserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
        //内存中缓存权限数据
        User.UserBuilder userBuilder = User.builder();
        UserDetails admin = userBuilder.username("admin").password(passwordEncoder.encode("123456")).roles("USER", "ADMIN").build();
        // 输出加密密码
        String encodePassword = passwordEncoder.encode("123456");
        log.info("encodePassword:{}", encodePassword);
        log.info("Crate ReactiveUserDetailsService::::");
        return new MapReactiveUserDetailsService(admin);
    }

    @Bean
    public ReactiveAuthenticationManager reactiveAuthenticationManager(ReactiveUserDetailsService userDetailsService,
                                                                       PasswordEncoder passwordEncoder) {
        log.info("reactiveAuthenticationManager::::::::::::::");
        UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
        authenticationManager.setPasswordEncoder(passwordEncoder);
        return authenticationManager;
    }
    /**
     * 设置了两个异常处理authenticationEntryPoint和accessDeniedHandler
     * @param http
     * @param tokenProvider
     * @param reactiveAuthenticationManager
     * @return
     */
    @Bean
    public SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http,
                                                JwtTokenProvider tokenProvider,
                                                ReactiveAuthenticationManager reactiveAuthenticationManager) {
        
        log.info("springWebFilterChain::::::::::::::::::::");    
        return http
                .cors().configurationSource(corsConfigurationSource())
                .and()
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .authenticationManager(reactiveAuthenticationManager)
                .exceptionHandling()
                .authenticationEntryPoint((swe, e) -> { //未认证异常
                    swe.getResponse().setStatusCode(HttpStatus.OK);
                    return swe.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap(JSON.toJSONString(ResponseData.noLoginError()).getBytes())));
                })
                .accessDeniedHandler((swe, e) -> { //无权访问异常
                    //swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
                    //return swe.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap("FORBIDDEN".getBytes())));
                    swe.getResponse().setStatusCode(HttpStatus.OK);
                    return swe.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap(JSON.toJSONString(ResponseData.forbiddenError()).getBytes())));
                })
                .and()
                .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
                .authorizeExchange(it -> it
                        //swagger-ui文档
                        .pathMatchers(HttpMethod.GET, "/doc/**").permitAll()
                        .pathMatchers(HttpMethod.POST, "/doc/**").permitAll()
                        .pathMatchers(HttpMethod.GET, "/v3/api-docs").permitAll()

                        //登录
                        .pathMatchers(HttpMethod.POST, "/user/login").permitAll()

                        //.pathMatchers(HttpMethod.POST, "/auth/login").permitAll()
                        // .pathMatchers(HttpMethod.GET, "/admin").hasRole("ADMIN")
                         .pathMatchers(HttpMethod.GET, "/user/monoJust").hasRole("USER")
                         .pathMatchers(HttpMethod.GET, "/user/getOneUser").hasAnyAuthority("user.view")
                        .anyExchange().authenticated()
                )
                //添加过滤器
                .addFilterAt(new JwtTokenAuthenticationFilter(tokenProvider), SecurityWebFiltersOrder.HTTP_BASIC)
                .build();
    }

      /**
     * 跨域配置
     * @return
     */
    private CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source =   new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");	//同源配置，*表示任何请求都视为同源，若需指定ip和端口可以改为如“localhost：8080”，多个以“，”分隔；
        corsConfiguration.addAllowedHeader("Content-Type,AccessToken,X-CSRF-Token, Authorization, Token");    //header，允许哪些header
        corsConfiguration.addAllowedMethod("POST, GET, OPTIONS, DELETE, PUT");	//允许的请求方法，PSOT、GET等
        corsConfiguration.setAllowCredentials(true);
        //允许读取的响应头
        corsConfiguration.addExposedHeader("Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type, Date, token, Token");
        source.registerCorsConfiguration("/**",corsConfiguration); //配置允许跨域访问的url
        return source;
    }
}
